package com.config;

import com.authentication.convertor.LoginAuthenticationConvertor;
import com.authorization.TokenParserFilter;
import com.authentication.manager.InMemoryAuthenticationManager;
import com.authentication.successhandler.CreatingTokenSuccessHandler;
import com.authorization.storage.AuthorityStorageManager;
import com.authorization.storage.InMemoryAuthorityStorageManager;
import com.bean.WebSecurityCustomizer;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.aopalliance.intercept.MethodInvocation;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
import org.springframework.security.web.authentication.AuthenticationFilter;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.savedrequest.CookieRequestCache;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.DelegatingFilterProxy;

import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

import static org.springframework.http.HttpStatus.FORBIDDEN;

@Configuration
public class SecurityConfig {

    private final String loginPath = "/user/login";
    /**
     * 默认情况下，只要一个 {@link Filter} 标记了 {@link Bean} 或者 {@link Component} 注解，
     * 那么他就会被spring自动注册到 tomcat 中。
     * <p>
     * 可以将容器中的 {@link Filter} 再次注入到 FilterRegistrationBean，
     * 并设置 filterRegistrationBean.setEnabled(false);
     * 来保证 {@link Filter} 不会被注册到 tomcat 中
     * 这样一来就能让 Filter 享受到 spring 的自动注入功能
     * <p>
     * 参考： https://docs.spring.io/spring-security/reference/5.8/servlet/architecture.html
     * Be careful when you declare your filter as a Spring bean, either by annotating it with @Component or by declaring it as a bean in your configuration, because Spring Boot will automatically register it with the embedded container. That may cause the filter to be invoked twice, once by the container and once by Spring Security and in a different order.
     * <p>
     * If you still want to declare your filter as a Spring bean to take advantage of dependency injection for example, and avoid the duplicate invocation, you can tell Spring Boot to not register it with the container by declaring a FilterRegistrationBean bean and setting its enabled property to false:
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean(DelegatingFilterProxy delegatingFilterProxy) {


        FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(delegatingFilterProxy);
        filterRegistrationBean.addUrlPatterns("/*");
//        filterRegistrationBean.setEnabled(false);
        return filterRegistrationBean;
    }

    @Bean
    public DelegatingFilterProxy delegatingFilterProxy() {
        DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy();
        delegatingFilterProxy.setTargetBeanName("filterChainProxy");

        return delegatingFilterProxy;
    }

    @Bean
    public FilterChainProxy filterChainProxy() {

        return new FilterChainProxy(Arrays.asList(
                loginFilterChain(),
                authorizationFilterChain()
        ));
    }


    /**
     * 登录所使用的过滤器链
     *
     * @return
     */
    @Bean
    public SecurityFilterChain loginFilterChain() {



        AntPathRequestMatcher allMatch = new AntPathRequestMatcher(loginPath);


        ExceptionTranslationFilter exceptionTranslationFilter = exceptionTranslationFilter();

        Filter authenticationFilter = authenticationFilter();


        return new DefaultSecurityFilterChain(allMatch, Arrays.asList(
                characterEncodingFilter(),
                exceptionTranslationFilter,
                authenticationFilter

        ));
    }

    @NotNull
    private Filter authenticationFilter() {
        /*UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilter(
                authenticationManager()
        );

        usernamePasswordAuthenticationFilter.setUsernameParameter("userName");

        usernamePasswordAuthenticationFilter.setPasswordParameter("password");

        usernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(
                creatingTokenSuccessHandler()
        );

        usernamePasswordAuthenticationFilter.setRequiresAuthenticationRequestMatcher(
                new AntPathRequestMatcher(loginPath,
                        "POST")
        );
        return usernamePasswordAuthenticationFilter;*/

        // 可以使用类似的 AuthenticationFilter 来实现登陆认证操作
        AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager(), new LoginAuthenticationConvertor());
        authenticationFilter.setSuccessHandler( creatingTokenSuccessHandler());

        return authenticationFilter;

    }

    /**
     * 避免 spring 将其自动注入到 tomcat 或者 {@link SecurityFilterChain} 中
     * @return
     */
    @Bean
    public FilterRegistrationBean<Filter> authenticationFilterRegistrationBean() {


        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();

        filterRegistrationBean.setFilter(authenticationFilter());
        filterRegistrationBean.setEnabled(false);
        return filterRegistrationBean;
    }

    @Bean
    public ExceptionTranslationFilter exceptionTranslationFilter() {
        return new ExceptionTranslationFilter(
                new HttpStatusEntryPoint(FORBIDDEN), new CookieRequestCache()

        );
    }

    @Bean
    public AuthorityStorageManager<GrantedAuthority> authorityStorageManager() {
        return new InMemoryAuthorityStorageManager();
    }

    @Bean
    public SecurityFilterChain authorizationFilterChain() {


        return new DefaultSecurityFilterChain(
                new AntPathRequestMatcher("/**"),
                Arrays.asList(
                        characterEncodingFilter(),
                        exceptionTranslationFilter(),
                        tokenParserFilter(),
                        new AuthorizationFilter(authorizationManager())
                )
        );


    }

    @Bean
    public TokenParserFilter tokenParserFilter() {
        return new TokenParserFilter(authorityStorageManager());
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        return new InMemoryAuthenticationManager();
    }

    @Bean
    public AuthorizationManager<HttpServletRequest> authorizationManager() {
//        AuthorizationManager<MethodInvocation> manager = new PreAuthorizeAuthorizationManager();

        return RequestMatcherDelegatingAuthorizationManager.builder()
                .add(
                        AnyRequestMatcher.INSTANCE, new AuthenticatedAuthorizationManager<>()//webExpressionAuthorizationManager()
                )
                .build();

    }

    /*@Bean
    public WebExpressionAuthorizationManager webExpressionAuthorizationManager(){
        WebExpressionAuthorizationManager webExpressionAuthorizationManager = new WebExpressionAuthorizationManager(
                "@webSecurityCustomizer.check(authentication,request)"
        );

        return webExpressionAuthorizationManager;
    }



    @Bean
    public WebSecurityCustomizer webSecurityCustomizer(){
        return new WebSecurityCustomizer();
    }
*/
    @Bean
    public CreatingTokenSuccessHandler creatingTokenSuccessHandler() {
        return new CreatingTokenSuccessHandler(authorityStorageManager());
    }

    @Bean
    public Filter characterEncodingFilter() {
        return new CharacterEncodingFilter(StandardCharsets.UTF_8.name());
    }
}
